<?php

require_once('php/lib/configured_object.php');

config('Model', 'model');

/**
 * Abstract model.
 *
 * Note: query methods remove the field prefix from the result.
 * Note: insert/update methods will add field prefixes (but not in where clause!).
 *
 * @author Pascal de Vink
 */
class Model extends ConfiguredObject {
	
	/**
	 * Database for model.
	 *
	 * @var Database
	 * @access public
	 */
	var $database = NULL;

	/**
	 * Table name. Defaults to classname in lowercase.
	 *
	 * @var string
	 * @access public
	 */
	var $table_name = NULL;
	
	
		
	/**
	 * Table prefix.
	 *
	 * @var string
	 * @access public
	 */
	var $table_prefix = NULL;
	
	/**
	 * Field prefix used in native field names. Defaults to <tablename minus 's'>_
	 *
	 * @var string
	 * @access public
	 */
	var $field_prefix = NULL;
	
	/**
	 * List of fields that should not be prefixed with the field prefix.
	 *
	 * @var array
	 * @access public
	 */
	var $do_not_prefix = array();
	
	/**
	 * Fields to be used in all queries. Defaults to all fields, i.e. '*'
	 *
	 * @var string
	 * @access public
	 */
	var $query_fields = NULL;	
	
	/**
	 * ID field name (without prefix). Defaults to 'id'.
	 *
	 * @var string
	 * @access public
	 */
	var $id_field = 'id';
	
	/**
	 * ID field name (without prefix). Defaults to 'id'.
	 *
	 * @var string
	 * @access public
	 */
	var $name_field = 'name';
		
	/**
	 * Constructs new instance
	 *
	 * @param Database $database Database for model.
	 */
	function __construct(&$database) {
		parent::__construct();

		# check for database
		if (!$database) die('No database specified for model (' . get_class($this) . ')');
		$this->database =& $database;
		
		# check for default properties
		if ($this->table_name === NULL) $this->table_name = strtolower(get_class($this));
		if ($this->field_prefix === NULL) $this->field_prefix = preg_replace('/s$/', '', $this->table_name) . '_';
		if ($this->query_fields === NULL) $this->query_fields = $this->table() . '.*';
	}
		
	/**
	 * Returns first record for given query.
	 *
	 * @param $query string Query
	 * @return array Record fields or NULL if not found or error occured
	 * @access public
	 */
	function &get($query) {
		if (!$query) return $this->getById();
		$record =& $this->database->queryFirst($query);
				
		return is_null($record)? NULL : array_strip($this->field_prefix, $record);
	}

	/**
	 * Returns record with given id.
	 *
	 * @param integer $id Id of record
	 * @return array Record fields or NULL if not found or error occured
	 * @access public
	 */
	function &getById($id) {
		$query = 'select ' . $this->queryFields() .
				 ' from ' . $this->table() .
				 ' where ' . $this->equals($this->field($this->id_field), $id);
				 
		//printf($query . '<br />');
				 
		return $this->get($query);
	}
	
	/**
	 * Returns record with given id.
	 *
	 * @param string $name Name of record
	 * @return array Record fields or NULL if not found or error occured
	 * @access public
	 */
	function &getByName($name) {
		$query = 'select ' . $this->queryFields() .
				 ' from ' . $this->table() .
				 ' where ' . $this->equals($this->field($this->name_field), $name);
		return $this->get($query);
	}


	/**
	 * Returns record with given id.
	 *
	 * @param string $name Name of record
	 * @return array Record fields or NULL if not found or error occured
	 * @access public
	 */
	function &getByLogin($username) {
		$query = 'select ' . $this->queryFields() .
				 ' from ' . $this->table() .
				 ' where ' . $this->equals($this->field($this->name_field), $username);
			 
		return $this->get($query);	
	}
	
	
	/**
	 * Returns resultset for given query.
	 *
	 * @param $query string Query
	 * @param int $limit Maximum number of records to return
	 * @param int $offset First record to return
	 * @return ResultSet Resultset for query or NULL if error occured
	 * @access public
	 */
	function &query($query, $limit = NULL, $offset = NULL) {
		$result =& $this->database->query($query, $limit, $offset);
		return is_null($result)? NULL : new ModelResultSet($this, $result);
	}
	
	/**
	 * Returns all records for given query (or all records if no query specified).
	 *
	 * @param $query string Query
	 * @param int $limit Maximum number of records to return
	 * @param int $offset First record to return
	 * @return array List of all records or empty list if not found or error occured
	 * @access public
	 */
	function &all($query = NULL, $limit = NULL, $offset = NULL) {
		//echo('<br> QUERY:'.$query.'<br>');
		if (is_null($query)) $query = 'select ' . $this->queryFields() . ' from ' . $this->table();
		
		$records =& $this->database->queryAll($query, $limit, $offset);
		if (is_null($records)) return array();
		
		$stripped_records = array();
		foreach ($records as $record) {
			$stripped_records[] = array_strip($this->field_prefix, $record);
		}
				
		return $stripped_records;	
	}
	
	/**
	 * Returns all records orders by given field.
	 *
	 * @param array $order_by Fields for order by clause
	 * @param int $limit Maximum number of records to return
	 * @param int $offset First record to return
	 * @return array List of all records or empty list if not found or error occured
	 * @access public
	 */
	function &allOrderedBy($order_by = NULL, $where = NULL, $limit = NULL, $offset = NULL) {
		$query = 'select ' . $this->queryFields() . ' from ' . $this->table();

		if (!is_array($order_by)) $order_by = array($order_by);

		if (!empty($where)) {
			$query .= ' where ' . $where;
		}
		if (!empty($order_by)) {		
			$query .= ' order by ';		
			$first = true;
			foreach ($order_by as $field) {
				if ($first) $first = false; else $query .= ',';
				if (strpos($field, ' desc') !== false) {
					$fieldname = str_replace(' desc', '', $field);
					$query .= $this->field($fieldname) . ' desc';
				} else {
					$query .= $this->field($field);
				}
			}
		}
			
		return $this->all($query, $limit, $offset);		
	}
	
	/**
	 * Performs insert of given fields in table.
	 *
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @return int Last generated unique ID or false if failed
	 * @access public
	 */
	function insert(&$fields) {	
		$new_fields = array();
		foreach ($fields as $key => $value) {
			if (substr($key, 0, 1) == '_') continue;
			$new_fields[$this->field($key)] = $value;
		}
		
		$last_id = $this->database->insert($this->table(), $new_fields);
		if (!$last_id) return false;
		
		$fields[$this->id_field] = $last_id;
		return $last_id;
	}

	/**
	 * Performs update of given fields.
	 *
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @param string $where Where clause
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function update(&$fields, $where = NULL) {
		if (!$where) return $this->updateById($fields);
		
		$new_fields = array();
		foreach ($fields as $key => $value) {
			if (substr($key, 0, 1) == '_') continue;
			$new_fields[$this->field($key)] = $value;
		}
		return $this->database->update($this->table(), $new_fields, $where);
	}

	/**
	 * Performs update of given fields in all records.
	 *
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function updateAll(&$fields) {		
		$new_fields = array();
		foreach ($fields as $key => $value) {
			if (substr($key, 0, 1) == '_') continue;
			$new_fields[$this->field($key)] = $value;
		}
		return $this->database->update($this->table(), $new_fields);
	}

	/**
	 * Performs update of given fields for record with given id.
	 *
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @param integer $id Id of record
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function updateById(&$fields, $id = NULL) {
		if (is_null($id)) $id = $fields[$this->id_field];
		
		$new_fields = array();
		foreach ($fields as $key => $value) {
			if (substr($key, 0, 1) == '_') continue;
			$new_fields[$this->field($key)] = $value;
		}
		$where = $this->equals($this->field($this->id_field), $id);
		return $this->database->update($this->table(), $new_fields, $where);
	}

	/**
	 * Performs update of given fields for record with given name.
	 *
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @param string $name Name of record
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function updateByName(&$fields, $name = NULL) {
		if (is_null($name)) $name = $fields[$this->name_field];
		
		$new_fields = array();
		foreach ($fields as $key => $value) {
			if (substr($key, 0, 1) == '_') continue;
			$new_fields[$this->field($key)] = $value;
		}
		$where = $this->equals($this->field($this->name_field), $name);
		return $this->database->update($this->table(), $new_fields, $where);
	}
		
	/**
	 * Performs delete in given table.
	 *
	 * @param string $where Where clause
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function delete($where) {
		return $this->database->delete($this->table(), $where);
	}
	
	/**
	 * Deletes all records from table.
	 *
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function deleteAll() {
		return $this->database->delete($this->table());
	}
	
	/**
	 * Deletes record with given id.
	 *
	 * @param integer $id Id of record
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function deleteById($id) {
		$where = $this->equals($this->field($this->id_field), $id);
		return $this->database->delete($this->table(), $where);
	}
	
	/**
	 * Deletes record with given name.
	 *
	 * @param string $name Name of record
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function deleteByName($name) {
		$where = $this->equals($this->field($this->name_field), $name);
		return $this->database->delete($this->table(), $where);
	}

	/**
	 * Returns default query fields.
	 *
	 * @return string Query fields.
	 */
	function queryFields() {
		return $this->query_fields;
	}
	
	/**
	 * Returns native table name.
	 *
	 * @param string $name Optional table name.
	 * @return string Native tabel name
	 */
	function table($table_name = NULL) {
		if (!$table_name) $table_name = $this->table_name;
		return $this->table_prefix . $table_name;
	}
	
	/**
	 * Returns native field name.
	 *
	 * @param string $name Field name.
	 * @return string Native field name
	 */
	function field($field_name) {
		return (is_array($this->do_not_prefix) && in_array($field_name, $this->do_not_prefix))? $field_name : ($this->field_prefix . $field_name);
	}
	
	/**
	 * Returns 'equals-clause'.
	 *
	 * @param string $field Native field name.
	 * @param mixed $value Field value.
	 * @return string Equals clause.
	 * @access public
	 */
	function equals($field, $value) {
		if (is_null($value) || empty($value)) 
			return $field . ' IS NULL';
		else
			return $field . ' = ' . $this->database->encode($value);
	}
}
	
class ModelResultSet extends Object {
	
	/**
	 * Model for resultset.
	 *
	 * @var Model
	 * @access public
	 */
	var $model = NULL;
	
	/**
	 * Native resultset.
	 *
	 * @var ResultSet
	 * @access public
	 */
	var $resultset = NULL;
	
	/**
	 * Constructs new instance
	 *
	 * @param Model $model Model for resultset.
	 */
	function __construct(&$model, &$resultset) {
		parent::__construct();
		$this->model =& $model;
		$this->resultset =& $resultset;
	}
		
	/**
	 * Fetches next row from resultset.
	 *
	 * @return array Associative array containing result or false when at end
	 * @access public
	 */
	function &next() {
		$record =& $this->resultset->next();
		return (is_null($record))? NULL : array_strip($this->model->field_prefix, $record);
	}

	/**
	 * Returns number of records in result.
	 *
	 * @return int Number of records
	 * @access public
	 */
	function count() {
		return $this->resultset->count();
	}
	
	/**
	 * Returns indicator if resultset is empty.
	 *
	 * @return boolean True if resultset is empty otherwise false
	 * @access public
	 */
	function isEmpty() {
		return $this->resultset->isEmpty();
	}
}
?>